package cn.com.bluemoon.daps.system.service.impl;


import cn.com.bluemoon.daps.common.constant.DapConstant.DapModel.NameRecommendFieldType;
import cn.com.bluemoon.daps.common.domain.BaseModel;
import cn.com.bluemoon.daps.common.domain.ResultBean;
import cn.com.bluemoon.daps.common.enums.BmStatus;
import cn.com.bluemoon.daps.common.mp.IBmService;
import cn.com.bluemoon.daps.common.toolkit.CheckUtils;
import cn.com.bluemoon.daps.system.entity.DapDataModelNameRecommendation;
import cn.com.bluemoon.daps.system.entity.DapSystemNameAbbr;
import cn.com.bluemoon.daps.system.mapper.DapDataModelNameRecommendationMapper;
import cn.com.bluemoon.daps.system.service.DapDataModelNameRecommendationService;
import cn.com.bluemoon.daps.system.service.DapSystemNameAbbrService;
import cn.com.bluemoon.daps.system.utils.DictionaryConfig;
import cn.com.bluemoon.daps.system.utils.ImportDictionaryExcel;
import cn.com.bluemoon.daps.system.vo.ModelNameRecommendSearchVo;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.hankcs.hanlp.corpus.tag.Nature;
import com.hankcs.hanlp.dictionary.CoreDictionary;
import com.hankcs.hanlp.seg.common.Term;
import com.hankcs.hanlp.tokenizer.StandardTokenizer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

/**
 * <p>
 * 命名推荐表 服务实现类
 * 结合nlp分词+字典
 * 结合谷歌翻译提供英文名
 *
 * </p>
 *
 * @author admin
 * @since 2020-08-03
 */
@Slf4j
@Service
public class DapDataModelNameRecommendationServiceImpl extends ServiceImpl<DapDataModelNameRecommendationMapper, DapDataModelNameRecommendation>
        implements DapDataModelNameRecommendationService {
    /**
     * 针对字典数据中，限制最大50个，避免调用谷歌翻译过渡被拉黑
     */
    public static final long LIMIT_QUERY_DIC_KEYWORD_SIZE = 50L;
    /**
     * 定义来源
     */
    public static final String DICT = "系统中文件字典数据";
    public static final String INTERNET = "互联网翻译数据";
    public static final String DB = "系统中库表基础字典数据";
    /**
     * 缓存英文全称+简称映射数据
     */
    public static final Cache<String, Optional<String>> EN_FULL_AND_ABBR_CACHE = CacheBuilder.newBuilder()
            .initialCapacity(2000)
            .expireAfterAccess(1, TimeUnit.DAYS)
            .build();
    @Resource
    private DapSystemNameAbbrService nameAbbrService;

    //    @PostConstruct
    public void init() {
        // 启动初始化字典数据
        Map<String, Double> tfIdfMap = DictionaryConfig.INSTANCE.getTf_IDFMap();
        log.info("启动初始化字典数据, size:{}", tfIdfMap.size());
    }

    /**
     * 智能推荐
     *
     * @param searchVo 查询条件
     * @return
     */
    @Override
    public Tuple2<BaseModel<DapDataModelNameRecommendation>, IPage<DapDataModelNameRecommendation>> nameRecommend(ModelNameRecommendSearchVo<DapDataModelNameRecommendation> searchVo) {
        List<Integer> types = Lists.newArrayList();
        //noinspection AliControlFlowStatementWithoutBraces
        if (Boolean.TRUE.equals(searchVo.getQualifierTerms())) {
            types.add(NameRecommendFieldType.限定词.getType());
        }
        //noinspection AliControlFlowStatementWithoutBraces
        if (Boolean.TRUE.equals(searchVo.getClassifierTerms())) {
            types.add(NameRecommendFieldType.类别词.getType());
        }
        //noinspection AliControlFlowStatementWithoutBraces
        if (Boolean.TRUE.equals(searchVo.getSubjectTerms())) {
            types.add(NameRecommendFieldType.主题词.getType());
        }
        final String keyword = StringUtils.trim(searchVo.getChiName());
        LambdaQueryWrapper<DapDataModelNameRecommendation> where = bmLambdaQuery()
                .like(StringUtils.isNotBlank(keyword), DapDataModelNameRecommendation::getChinName, keyword)
                .in(!types.isEmpty(), DapDataModelNameRecommendation::getFieldType, types);
        // 智能推荐词
        // 1.根据关键字获取库中相关数据
        // 2.使用“BM25 相关度算法实现” 进行计算，相关数据的权重(匹配度）取最匹配的作为你推荐词
        DapDataModelNameRecommendation recommendTerm = new DapDataModelNameRecommendation();
        IPage<DapDataModelNameRecommendation> page;
        if (StringUtils.isNotBlank(keyword)) {
            // 1.不分词查询数据库中，若存在，直接返回 若不存在，则分词处理
            where = bmLambdaQuery().eq(DapDataModelNameRecommendation::getChinName, keyword)
                    .in(!types.isEmpty(), DapDataModelNameRecommendation::getFieldType, types);
            List<DapDataModelNameRecommendation> dbNameRecommendByWrapper = findDbNameRecommendByWrapper(where);
            final List<DapDataModelNameRecommendation> allList = Lists.newArrayList();
            if (!dbNameRecommendByWrapper.isEmpty()) {
                allList.addAll(dbNameRecommendByWrapper);
            } else {
                List<Term> termByKeyword = MyCoreDictionary.findTermByKeyword(keyword);
                for (Term term : termByKeyword) {
                    String keywordS = term.word;
                    where = bmLambdaQuery().eq(DapDataModelNameRecommendation::getChinName, keywordS)
                            .in(!types.isEmpty(), DapDataModelNameRecommendation::getFieldType, types);
                    final List<DapDataModelNameRecommendation> list = findDbNameRecommendByWrapper(where).stream()
                            .distinct().collect(toList());
                    if (list.isEmpty()) {
                        DapDataModelNameRecommendation noneFind = new DapDataModelNameRecommendation();
                        noneFind.setChinName(keywordS);
                        noneFind.setEngFullName("");
                        noneFind.setEngName("");
                        noneFind.setFieldType(NameRecommendFieldType.其他词.getType());
                        noneFind.setId("");
                        list.add(noneFind);
                    }
                    allList.addAll(list);
                }
            }
            // 把分词的数据组合一个推荐词
            String recommendName = allList.stream().filter(d -> d != null && StringUtils.isNotBlank(d.getEngName())).map(DapDataModelNameRecommendation::getEngName).distinct().collect(Collectors.joining("_")).toLowerCase();
            recommendTerm.setChinName(keyword);
            recommendTerm.setEngName(recommendName);
            page = IBmService.logicPage(allList, searchVo.getCurrent(), searchVo.getPageSize());
        } else {
            page = page(searchVo.toMpPage(), where).convert(dapDataModelNameRecommendation -> {
                dapDataModelNameRecommendation.setDataSource(DB);
                return dapDataModelNameRecommendation;
            });
        }
        return Tuples.of(recommendTerm, page);
    }

    /**
     * 命名推荐列表
     * @param vo
     * @return
     */
    @Override
    public IPage<DapDataModelNameRecommendation> nameRecommendList(ModelNameRecommendSearchVo<DapDataModelNameRecommendation> vo) {

        LambdaQueryWrapper<DapDataModelNameRecommendation> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(DapDataModelNameRecommendation::getChinName,vo.getChiName())
                .eq(DapDataModelNameRecommendation::getBmStatus, BmStatus.ON.getCode())
                .orderByDesc(DapDataModelNameRecommendation::getUpdateTime);
        List<Integer> list = new ArrayList<>();
        if(vo.getClassifierTerms()){
            list.add(NameRecommendFieldType.类别词.getType());
        }
        if(vo.getQualifierTerms()){
            list.add(NameRecommendFieldType.限定词.getType());
        }
        if(vo.getSubjectTerms()){
            list.add(NameRecommendFieldType.主题词.getType());
        }
        if(!list.isEmpty()){
            wrapper.in(DapDataModelNameRecommendation::getFieldType, list);
        }
        IPage<DapDataModelNameRecommendation> page = this.getBaseMapper().selectPage(vo.getMpPage(), wrapper);
        return page;
    }

    /**
     * 导入文件模板
     * @param file
     * @return
     */
    @Override
    public ResultBean<?> importExcel(MultipartFile file) {

        // 解析excel文件
        ResultBean<?> result = ImportDictionaryExcel.readExcel(file);
        List<DapDataModelNameRecommendation> list = Lists.newArrayList();
        if(result.getCode() == 200){
            List<Map<String, Object>> content = (List<Map<String, Object>>)result.getContent();
            content.forEach(c -> {
                JSONObject jsonObject = (JSONObject) JSONObject.toJSON(c);
                DapDataModelNameRecommendation recommendation = JSONObject.toJavaObject(jsonObject, DapDataModelNameRecommendation.class);
                list.add(recommendation);
            });
            List<String> collect = list.stream().map(DapDataModelNameRecommendation::getChinName).collect(toList());
            List<String> duplicateElements = CheckUtils.getDuplicateElements(collect);
            if(duplicateElements.isEmpty()){
                List<DapDataModelNameRecommendation> nameList = this.getBaseMapper()
                        .selectList(new LambdaQueryWrapper<DapDataModelNameRecommendation>()
                        .eq(DapDataModelNameRecommendation::getBmStatus, BmStatus.ON.getCode()));
                List<String> names = nameList.stream().map(DapDataModelNameRecommendation::getChinName).collect(toList());
                List<String> intersection = collect.stream().filter(item -> names.contains(item)).collect(toList());
                // 批量插入
                if(intersection.isEmpty()){
                    return ResultBean.ok(this.bmSaveBatch(list));
                }
                List<String> repeat = intersection.stream().map(s -> "中文名重复:" + s).collect(toList());
                return ResultBean.error(repeat);
            }else {
                List<String> repeat = duplicateElements.stream().map(s -> "中文名重复:" + s).collect(toList());
                return ResultBean.error(repeat);
            }

        }
        return result;
    }

    /**
     * 根据英文全称获取简称(带缓存）
     *
     * @param engFullName 英文全称
     * @return 简称
     */
    public Optional<String> getEnAbbrNameByEnFullNameInCache(String engFullName) {
        try {
            LambdaQueryWrapper<DapSystemNameAbbr> eq = nameAbbrService.bmLambdaQuery()
                    .eq(DapSystemNameAbbr::getFullName, engFullName)
                    .orderByAsc(DapSystemNameAbbr::getCreateBy).last(" limit 1");
            return EN_FULL_AND_ABBR_CACHE.get(engFullName, () -> {
                DapSystemNameAbbr one = nameAbbrService.getOne(eq);
                return one != null ? Optional.ofNullable(one.getAbbrName()) : Optional.empty();
            });
        } catch (ExecutionException e) {
            log.error("获取{}缓存数据异常", engFullName, e);
        }
        return Optional.empty();
    }

    /**
     * 抽取依据调整获取词列表
     *
     * @param where 条件
     * @return 抽取依据调整获取词列表
     */
    public List<DapDataModelNameRecommendation> findDbNameRecommendByWrapper(LambdaQueryWrapper<DapDataModelNameRecommendation> where) {
        final List<DapDataModelNameRecommendation> list = list(where).stream().peek(d -> {
            d.setDataSource(DB);
            // 针对库表中存在的直接指定为最高权重
            d.setWeigthSize(Integer.MAX_VALUE);
        }).collect(toList());
        return list;
    }

    /**
     * 基于{@link CoreDictionary}
     * 第一次使用字典时会慢一点，因为需要初始化数据
     * <p>
     * 增加自定义方法
     */
    static class MyCoreDictionary extends CoreDictionary {
        /**
         * 主题、类别、限定词对应的词性
         */
        private static final ImmutableSet<Nature> TOPIC_NATURES = ImmutableSet.of(Nature.nl, Nature.nx, Nature.n, Nature.nr, Nature.nrj, Nature.nrf, Nature.nr2, Nature.ns, Nature.nsf, Nature.nt, Nature.ntc, Nature.nh, Nature.nn, Nature.ng, Nature.ni, Nature.nm, Nature.nmc, Nature.nb, Nature.nba, Nature.nbp, Nature.nz, Nature.vn, Nature.an, Nature.n, Nature.nr, Nature.nrj, Nature.nrf, Nature.nr, Nature.nr, Nature.ns, Nature.nsf, Nature.nt, Nature.ntc, Nature.ntcf, Nature.ntcb, Nature.ntch, Nature.nto, Nature.ntu, Nature.nts, Nature.nth, Nature.nh, Nature.nhm, Nature.nhd, Nature.nn, Nature.nnt, Nature.nnd, Nature.ng, Nature.nf, Nature.ni, Nature.nit, Nature.nic, Nature.nis, Nature.nm, Nature.nmc, Nature.nb, Nature.nba, Nature.nbc, Nature.nbp, Nature.nz);
        private static final ImmutableSet<Nature> NUMBER_NATURES = ImmutableSet.of(Nature.qg, Nature.mq, Nature.q, Nature.qv, Nature.qt);
        private static final ImmutableSet<Nature> TYPE_NATURES = ImmutableSet.of(Nature.bg, Nature.gbc, Nature.b, Nature.bl, Nature.gbc, Nature.Mg, Nature.dg);
        private static final ImmutableSet<Nature> V_NATURES = ImmutableSet.of(Nature.v, Nature.vd, Nature.vn, Nature.vshi, Nature.vyou, Nature.vf, Nature.vx, Nature.vi, Nature.vl, Nature.vg);
        private static final ImmutableSet<Nature> R_NATURES = ImmutableSet.of(Nature.r, Nature.rr, Nature.rz, Nature.rzt, Nature.rzs, Nature.rzv, Nature.ry, Nature.ryt, Nature.rys, Nature.ryv, Nature.rg, Nature.Rg);
        private static final ImmutableSet<Nature> A_NATURES = ImmutableSet.of(Nature.a, Nature.an, Nature.ad, Nature.ag, Nature.al);

        private MyCoreDictionary() {

        }

        /**
         * 词性转type
         *
         * @param nature nature
         * @return
         */
        private static int ofNature(Nature nature) {
            if (TOPIC_NATURES.contains(nature)) {
                return NameRecommendFieldType.主题词.getType();
            }
            if (NUMBER_NATURES.contains(nature)) {
                return NameRecommendFieldType.限定词.getType();
            }
            if (TYPE_NATURES.contains(nature)) {
                return NameRecommendFieldType.类别词.getType();
            } else {
                return NameRecommendFieldType.其他词.getType();
            }
        }

        /**
         * 前缀查询
         *
         * @param key
         * @return
         */
        private static LinkedList<Map.Entry<String, Attribute>> commonPrefixSearch(String key) {
            LinkedList<Map.Entry<String, Attribute>> entries = trie.commonPrefixSearchWithValue(key.toCharArray(), 0);
            // 针对全匹配的给最大权重
            entries.stream().filter(m -> m.getKey().equalsIgnoreCase(key)).forEach(m -> {
                Attribute value = m.getValue();
                value.frequency = new int[]{Integer.MAX_VALUE};
                value.totalFrequency = Integer.MAX_VALUE;
            });
            return entries;
        }

        /**
         * @param keyword 请求词
         * @return 返回分词数据
         */
        public static List<Term> findTermByKeyword(String keyword) {
            List<Term> segment = StandardTokenizer.segment(keyword);
//            List<Term> segment = NLPTokenizer.segment(keyword);
            return segment.stream().filter(term -> (TOPIC_NATURES.contains(term.nature) ||
                    NUMBER_NATURES.contains(term.nature) ||
                    TYPE_NATURES.contains(term.nature) ||
                    V_NATURES.contains(term.nature) ||
                    R_NATURES.contains(term.nature)) ||
                    A_NATURES.contains(term.nature))
                    .collect(toList());
        }
    }

}
